package mirrormonkey.framework;

import java.lang.reflect.Constructor;
import java.util.LinkedHashMap;
import java.util.Map;

import mirrormonkey.framework.module.MirrorMonkeyModule;
import mirrormonkey.util.netevent.EventManager;
import mirrormonkey.util.serializer.ColorRGBASerializer;
import mirrormonkey.util.serializer.QuaternionSerializer;

import com.jme3.app.Application;
import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.network.serializing.Serializer;
import com.jme3.renderer.RenderManager;
import com.jme3.system.NanoTimer;
import com.jme3.system.Timer;

public abstract class SyncAppState<M extends EventManager> implements AppState {

	public final EntityProvider entityProvider;

	public final M eventManager;

	public final Map<Class<?>, MirrorMonkeyModule> modules;

	protected final Timer timer;

	public SyncAppState(EntityProvider entityProvider, M eventManager) {
		Serializer.registerClass(Quaternion.class, new QuaternionSerializer());
		Serializer.registerClass(ColorRGBA.class, new ColorRGBASerializer());
		entityProvider.setAppState(this);
		this.entityProvider = entityProvider;
		this.eventManager = eventManager;
		modules = new LinkedHashMap<Class<?>, MirrorMonkeyModule>();
		timer = new NanoTimer();
	}

	public <T extends MirrorMonkeyModule> T getModule(Class<T> moduleClass) {
		try {
			// TODO if there is any data registered, this must fail
			T module = (T) modules.get(moduleClass);
			if (module == null) {
				ConstrSearch: for (Class<?> c = getClass(); !c
						.equals(Object.class); c = c.getSuperclass()) {
					for (Constructor<?> constr : moduleClass.getConstructors()) {
						if (constr.getParameterTypes().length == 1
								&& constr.getParameterTypes()[0].equals(c)) {
							module = (T) constr.newInstance(this);
							break ConstrSearch;
						}
					}

				}
				if (module == null) {
					throw new NoSuchMethodException(
							"Could not find constructor for module class: "
									+ moduleClass);
				}
				modules.put(moduleClass, module);
			}
			return module;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void update(float tpf) {
		eventManager.processEvents();
		entityProvider.update(tpf);
		for (MirrorMonkeyModule i : modules.values()) {
			i.update(tpf);
		}
		timer.update();
	}

	public final long getSyncTime() {
		return timer.getTime();
	}

	@Override
	public void cleanup() {
	}

	@Override
	public void initialize(AppStateManager arg0, Application arg1) {
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

	@Override
	public boolean isInitialized() {
		return true;
	}

	@Override
	public void postRender() {
	}

	@Override
	public void render(RenderManager arg0) {
	}

	@Override
	public void setEnabled(boolean arg0) {
	}

	@Override
	public void stateAttached(AppStateManager arg0) {
	}

	@Override
	public void stateDetached(AppStateManager arg0) {
	}

	protected abstract boolean isCollectLocalConstructors();

}
